require 'rest_client'
require 'json'

module Installr::Client::Command
  class Agent < Base
    def create(agent_name, agent_ip_address)
      puts "[agent:create] create agent #{agent_name} with #{agent_ip_address}" if @options[:verbose]
      @log.info("[agent:create] create agent #{agent_name} with #{agent_ip_address}")

      unless @options[:user]
        puts "[agent:create] --user option is mandatory"
        @log.error("[agent:create] --user option is mandatory")
        return -1
      end

      if @options[:auth_file] != nil
	if File.exist?(@options[:auth_file])
          pem = ''
          file = File.new(@options[:auth_file], "r")
          file.each_line { |line| pem += line }
	else
          puts "[agent:create] cannot find auth file #{@options[:auth_file]}"
          @log.error("[agent:create] cannot find auth file #{@options[:auth_file]}")
          return -1
        end
      else
        pem = nil
      end

      @options[:remote_port] = '4567' unless @options[:remote_port]
      @options[:remote_upload_path] = "/tmp" unless @options[:remote_upload_path]

      if @storage.create_agent(agent_name, agent_ip_address, @options[:user], @options[:password], pem, @options[:passphrase], @options[:remote_port], @options[:remote_upload_path]) != 0
        puts "[agent:create] cannot create agent #{agent_name}. Check the list of agents."
        @log.error("[agent:create] cannot create agent #{agent_name}. Check the list of agents.")
	return -1
      end
      return 0
    end

    def delete(agent_name)
      puts "[agent:delete] delete agent #{agent_name}" if @options[:verbose]
      @log.info("[agent:delete] delete agent #{agent_name}")

      if @storage.delete_agent(agent_name) != 0
        puts "[agent:delete] cannot delete agent #{agent_name}. Check the list of agents."
        @log.error("[agent:delete] cannot delete agent #{agent_name}. Check the list of agents.")
	return -1
      end
      return 0
    end

    def info(agent_name)
      puts "[agent:info] info agent #{agent_name}" if @options[:verbose]
      @log.info("[agent:info] info agent #{agent_name}")

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[agent:info] cannot find agent #{agent_name}"
        @log.error("[agent:info] cannot find agent #{agent_name}")
	return -1
      else
        puts "information of the agent #{agent_name}: "
        puts "address: #{agent[0]}"
        puts "account: #{agent[1]}"
        if agent[3]
          puts "  with certification"
        else
          puts "  with password"
        end
	puts "port: #{agent[5]}"
	puts "remote_upload_path: #{agent[6]}"
      end
      puts " ---"
      return 0
    end

    def list
      puts "[agent:list] list agents" if @options[:verbose]
      @log.info("[agent:list] list agents")

      agents = @storage.list_agents(nil)
      unless agents
        puts "cannot find any agent"
      else
        puts "current agent listings: "
        agents.each { |agent| print "#{agent} " }
      end
      puts " ---"
      return 0
    end

    def list_from_group(group_name)
      puts "[agent:list_from_group] list agents from group #{group_name}" if @options[:verbose]
      @log.info("[agent:list_from_group] list agents from group #{group_name}")

      agents = @storage.list_agents(group_name)
      unless agents
        puts "[agent:list_from_group] cannot find group #{group_name}"
        @log.error("[agent:list_from_group] cannot find group #{group_name}")
	return -1
      else
        puts "current agent listings from group #{group_name}: "
        agents.each { |agent| print "#{agent} " }
      end
      puts " ---"
      return 0
    end

    def add(agent_name, group_name)
      puts "[agent:add] add agent #{agent_name} to group #{group_name}" if @options[:verbose]
      @log.info("[agent:add] add agent #{agent_name} to group #{group_name}")

      unless @storage.check_agent(agent_name)
        puts "[agent:add] cannot find agent #{agent_name}"
        @log.error("[agent:add] cannot find agent #{agent_name}")
        return -1
      end

      unless @storage.check_group(group_name)
        puts "[agent:add] cannot find group #{group_name}"
        @log.error("[agent:add] cannot find group #{group_name}")
        return -1
      end

      agents = @storage.list_agents(group_name)
      if agents
        agents.each do
          |agent|
          if agent == agent_name
            puts "[agent:add] duplicated agent name #{agent_name}"
            @log.error("[agent:add] duiplicated agent name #{agent_name}")
	    return -1
          end
        end
      else
        puts "[agent:add] cannot find group #{group_name}"
        @log.error("[agent:add] cannot find group #{group_name}")
	return -1
      end

      if @storage.add_agent(agent_name, group_name) != 0
        puts "[agent:add] cannot add agent #{agent_name} to group #{group_name}"
        @log.error("[agent:add] cannot add agent_name #{agent_name} to group #{group_name}")
	return -1
      end
      return 0
    end

    def remove(agent_name, group_name)
      puts "[agent:remove] remove agent #{agent_name} from group #{group_name}" if @options[:verbose]
      @log.info("[agent:remove] remove agent #{agent_name} from group #{group_name}")

      unless @storage.check_agent(agent_name)
        puts "[agent:remove] cannot find agent #{agent_name}"
        @log.error("[agent:remove] cannot find agent #{agent_name}")
        return -1
      end

      unless @storage.check_group(group_name)
        puts "[agent:remove] cannot find group #{group_name}"
        @log.error("[agent:remove] cannot find group #{group_name}")
        return -1
      end

      if @storage.remove_agent(agent_name, group_name) != 0
        puts "[agent:remove] cannot remove agent #{agent_name} from group #{group_name}"
        @log.error("[agent:remove] cannot remove agent_name #{agent_name} from group #{group_name}")
	return -1
      end
      return 0
    end

    def show_status(agent_name)
      puts "[agent:show_status] show status agent #{agent_name}" if @options[:verbose]
      @log.info("[agent:show_status] show status agent #{agent_name}")

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[agent:show_status] cannot find agent_name #{agent_name}"
        @log.error("[agent:show_status] cannot find agent_name #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[agent:show_status] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[agent:show_status] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      uri = "http://#{agent[0]}:#{remote_port}/system/info/all"
      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.get

      if response.code == 200
        status_result = JSON.parse(response.body)
        puts "--------------------------------------"
	puts "- information of agent #{agent_name} -"
        status = status_result[0]["status"]
        puts status
	puts "- app list of agent #{agent_name} -"
        status = status_result[0]["list"]
        puts status
        puts "--------------------------------------"
        return 0
      else
        puts "[agent:show_status] response code = #{response.code} from agent #{agent_name}"
        @log.error("[agent:show_status] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[agent:show_status] #{agent_name}: (exception) #{e}"
        @log.error("[agent:show_status] #{agent_name}: (exception) #{e}")
	return -2
    end

    def execute_bootstrap(agent_name)
      puts "[agent:execute_bootstrap] execute bootstrap agent #{agent_name}" if @options[:verbose]
      @log.info("[agent:execute_bootstrap] execute bootstrap agent #{agent_name}")

      agent = @storage.info_agent(agent_name)
      if agent
        if @options[:verbose]
          print "upload to " + agent[0] + "(" + agent[1] + ")"
          if agent[3]
            puts " with certificate"
          else
            puts " with password"
          end
        end

        if @options[:remote_upload_path]
          remote_upload_path = agent[6] = @options[:remote_upload_path]
        else
          remote_upload_path = agent[6]
        end

        if @options[:script_file] and File.exist?(@options[:script_file])
          puts "script file is #{@options[:script_file]}" if @options[:verbose]
          remote_script_file = remote_upload_path + '/' + File.basename(@options[:script_file])
        else
          puts "[agent:execute_bootstrap] cannot find script file"
          @log.error("[agent:execute_bootstrap] cannot find script file")
          return -1
        end
      else
        puts "[agent:execute_bootstrap] cannot find agent #{agent_name}"
        @log.error("[agent:execute_bootstrap] cannot find agent #{agent_name}")
        return -1
      end

      unless port_open?(agent[0], 22)
        puts "[agent:execute_bootstrap] #{agent_name}: ssh port cannot be connected"
        @log.error("[agent:execute_bootstrap] #{agent_name}: ssh port cannot be connected")
        return -2
      end

      ftp = Installr::SSH::FTP.new(@options)
      if agent[3]
        case ftp.open_with_cert(agent[0], agent[1], agent[3], agent[4])
          when -1
            puts "[agent:execute_bootstrap] cannot open agent #{agent[0]} with certificate"
            @log.error("[agent:execute_bootstrap] cannot open agent #{agent[0]} with certificate")
            return -1
          when -2
            puts "[agent:execute_bootstrap] authentication with certification for #{agent_name} failed"
            @log.error("[agent:execute_bootstrap] authentication with certification for #{agent_name} failed")
            return -1
        end
      else
        case ftp.open(agent[0], agent[1], agent[2])
          when -1
            puts "[agent:execute_bootstrap] cannot open agent #{agent[0]} with password"
            @log.error("[agent:execute_bootstrap] cannot open agent #{agent[0]} with password")
            return -1
          when -2
            puts "[agent:execute_bootstrap] authentication with password for #{agent_name} failed"
            @log.error("[agent:execute_bootstrap] authentication with password for #{agent_name} failed")
            return -1
        end
      end

      if @options[:local_upload_path] and File.directory?(@options[:local_upload_path])
        case ftp.upload_all(@options[:local_upload_path], remote_upload_path)
          when -3
            puts "[agent:execute_bootstrap] #{agent_name}: cannot mkdir remote upload directory #{remote_upload_path}"
            @log.error("[agent:execute_bootstrap] #{agent_name}: cannot mkdir remote upload directory file #{remote_upload_path}")
            return -1
          when -2
            puts "[agent:execute_bootstrap] #{agent_name}: cannot find local upload directory #{@options[:local_upload_path]}"
            @log.error("[agent:execute_bootstrap] #{agent_name}: cannot find local upload directory file #{@options[:local_upload_path]}")
            return -1
          when -1
            puts "[agent:bootstrap] #{agent_name}: cannot mkdir remote upload path #{remote_upload_path}"
            @log.error("[agent:bootstrap] #{agent_name}: cannot mkdir remote upload path #{remote_upload_path}")
            return -1
        end
      elsif @options[:local_upload_path] and !File.directory?(@options[:local_upload_path])
        puts "[agent:bootstrap] #{agent_name}: local upload path #{@options[:local_upload_path]} is not a directory"
       @log.error("[agent:bootstrap] #{agent_name}: local upload path #{@options[:local_upload_path]} is not a directory")
       return -1
      end

      if @options[:script_file]
        case ftp.upload(@options[:script_file], remote_script_file)
          when -2
            puts "[agent:execute_bootstrap] #{agent_name}: cannot find source file #{@options[:script_file]}"
            @log.error("[agent:execute_bootstrap] #{agent_name}: cannot find source file #{@options[:script_file]}")
            return -1
          when -1
            puts "[agent:bootstrap] #{agent_name}: cannot upload #{@options[:script_file]} to #{remote_script_file}"
            @log.error("[agent:bootstrap] #{agent_name}: cannot upload #{@options[:script_file]} to #{remote_script_file}")
            return -1
        end
      end
 
      ftp.close

      shell = Installr::SSH::SHELL.new(@options)
      if agent[3]
        case shell.open_with_cert(agent[0], agent[1], agent[3], agent[4])
          when -1
            puts "[agent:execute_bootstrap] cannot open agent #{agent[0]} with certificate"
            @log.error("[agent:execute_bootstrap] cannot open agent #{agent[0]} with certificate")
            return -1
          when -2
            puts "[agent:execute_bootstrap] authentication with certification for #{agent_name} failed"
            @log.error("[agent:execute_bootstrap] authentication with certification for #{agent_name} failed")
            return -1
        end
      else
        case shell.open(agent[0], agent[1], agent[2])
          when -1
            puts "[agent:execute_bootstrap] cannot open agent #{agent[0]} with password"
            @log.error("[agent:execute_bootstrap] cannot open agent #{agent[0]} with password")
            return -1
          when -2
            puts "[agent:execute_bootstrap] authentication with password for #{agent_name} failed"
            @log.error("[agent:execute_bootstrap] authentication with password for #{agent_name} failed")
            return -1
        end
      end

      if @options[:script_file]
        cmd = "cd #{remote_upload_path};sh #{remote_script_file}"
        #exit_code = shell.fast_exec(cmd)
        exit_code, output = shell.exec(cmd)
        puts "----- agent #{agent_name} -----------------"
        puts output
        if @options[:verbose]
          puts "------------"
          puts "exit_code is " + exit_code.to_s
          puts "------------"
        end
        puts "--------------------------------"
      end
      shell.close
      return 0

      rescue => e
        puts "[agent:execute_bootstrap] #{agent_name}: (exception) #{e}"
        @log.error("[agent:execute_bootstrap] #{agent_name}: (exception) #{e}")
        return -2
    end

    def log(agent_name)
      puts "[agent:log] show log from agent #{agent_name}" if @options[:verbose]
      @log.info("[agent:log] show log from agent #{agent_name}")

      agent = @storage.info_agent(agent_name)
      unless agent
        puts "[agent:log] cannot find agent_name #{agent_name}"
        @log.error("[agent:log] cannot find agent_name #{agent_name}")
        return -1
      end

      if @options[:remote_port]
        remote_port = @options[:remote_port].to_i
      else
        remote_port = agent[5].to_i
      end

      unless port_open?(agent[0], remote_port)
        puts "[agent:log] #{agent_name}: agent port #{remote_port} cannot be connected"
        @log.error("[agent:log] #{agent_name}: agent port #{remote_port} cannot be connected")
        return -2
      end

      remote_log_file = @options[:remote_log_file]
      if @options[:application_server_log]
        uri = "http://#{agent[0]}:#{remote_port}/system/log"
        remote_log_file = 'catalina.out' unless @options[:remote_log_file]
      else
        uri = "http://#{agent[0]}:#{remote_port}/self/log"
        remote_log_file = 'InstallrAgent.log' unless @options[:remote_log_file]
      end

      puts "uri = [#{uri}]" if @options[:verbose]

      resource = RestClient::Resource.new uri
      response = resource.post :remote_log_file => remote_log_file, :remote_log_length => @options[:remote_log_length]

      if response.code == 200
        log_result = JSON.parse(response.body)
        log = log_result[0]["result"]
        puts "----- #{agent_name} -----------------------------------------"
        puts log
        puts "-------------------------------------------------"
        return 0
      else
        puts "[agent:log] response code = #{response.code} from agent #{agent_name}"
        @log.error("[agent:log] response code #{response.code} from agent #{agent_name}")
        return -1
      end

      rescue => e
        puts "[agent:log] #{agent_name}: (exception) #{e}"
        @log.error("[agent:log] #{agent_name}: (exception) #{e}")
	return -2
    end
  end
end
